前言
在iOS中,大多数的APP都有不可或缺的图片资源,同时也很容易因为对图片的处理不恰当造成性能低下,不要让图片成为你的APP的性能杀手。
一张图片从磁盘中加载出来,同时显示到屏幕上,经过了一系列的复杂处理,其中包括了对图片的解码,我们今天就来做一次图片的解码实践。
图片显示过程
平时我们对于图片的显示,一般都是使用以下的代码:
1 | UIImage *image = [UIImage imageNamed:@"icon"]; |
简单的两行代码其实里面包含了下面几步:
- 首先会调用 image/io从磁盘中加载一张图片,这个时候,图片还没有解码。
- 将image复制给imageView的image
- 然后一个隐式的CATransaction捕获到图层树的变化
- 在主线程runloop下一次迭代到来的时候,Core Animation会提交这个隐式transaction,这个过程会对图片进行copy操作,根据图片的不同,可能会涉及以下的一些甚至全部的步骤。
I. 为文件管理IO和解压缩操作分配内存缓存区域 II. 从磁盘中读取数据到内存中 III. 将压缩的图片数据解码成未压缩的图片数据,这通常是一个非常频繁耗时的CPU操作 IV. CoreAnimation将未压缩位图数据渲染到layer上。
从上面的步骤可以看出,图片的解码主是主要耗时的原因,如果一个APP中只有几张图片是这样设置当然是没问题的,可是如果在一个TableView中有大量的图片在滚动,如果这个时候在不断的解码显示那必然会导致界面卡顿。
为什么需要解码
实际上我们使用的JPEG或者PNG格式的图片,都是一种经过压缩的位图图形格式,只不过PNG是无损压缩并且支持alpha通道,而JEPG是有损压缩,并且可以指定压缩比。下面是iOS中提供的获得上述格式图片的方法:
1 | UIKIT_EXTERN NSData * __nullable UIImagePNGRepresentation(UIImage * __nonnull image); // return image as PNG. May return nil if image has no CGImageRef or invalid bitmap format |
接下来我们就要了解一下位图:
A bitmap image (or sampled image) is an array of pixels (or samples). Each pixel represents a single point in the image. JPEG, TIFF, and PNG graphics files are examples of bitmap images.
其实位图就是一个像素数组,每个像素都代表了图片中独立的一个点,每一个点其实又包含了以下内容:
Bits per component :一个像素中每个独立的颜色分量使用的 bit 数;
Bits per pixel :一个像素使用的总 bit 数;
Bytes per row :位图中的每一行使用的字节数。
这里不多说,具体的可以查看像素格式。
我们知道位图是经过压缩后的,我们可以通过
1 | UIImage *image = [UIImage imageNamed:@"icon"]; |
得到原始的数据大小,一般该数据的大小的计算方式是:
图片像素宽 图片像素高 每个像素所占的字节数4
所以图片当前的大小并不等于解码后的大小,所以我们才需要解码之后才能得到原始的数据大小,只有使用原始数据才能正确的显示出图片。
正确的解码姿势
上面已经知道了图片显示是会在主线程解压缩图片之后然后渲染到屏幕上,首先我们可以在子线程中做解码的操作,在子线程中重新绘制图片,得到解码后的图片,然后渲染到屏幕上。
我们先上代码:
1 | - (void)decodeImage:(UIImage *)image completion:(void(^)(UIImage *image))completion{ |
性能对比
图片尺寸 | 未解码直接渲染时间(ms) | 解码后渲染时间(ms) |
---|---|---|
128x96.jpg | 0.99 | 0.08 |
128x96.png | 0.80 | 0.08 |
256x192.jpg | 3.01 | 0.14 |
256x192.png | 2.30 | 0.17 |
512x384.jpg | 4.83 | 0.28 |
512x384.png | 6.03 | 0.28 |
1024x768.jpg | 13.83 | 1.43 |
1024x768.png | 18.31 | 1.12 |
2048x1536.jpg | 31.72 | 3.99 |
2048x1536.png | 75.05 | 5.16 |
通过上面的对比我们可以看出,解码后的图片的渲染速度远远高于未解码后的图片的渲染速度,并且由于我们在子线程中进行解码,所以也不会造成主线程的UI卡顿。
Demo is here
最后,按照常规,这里应该有一份demo;